﻿unit Chromium4D.Handler.Classes;

interface

uses ceflib, System.SysUtils, System.Rtti, Winapi.Windows, System.Generics.Collections, System.Generics.Defaults
  , Chromium4D.Classes
  , Chromium4D.Render.Process
  , Chromium4D.Render
  , Chromium4D.Core
  , Chromium4D.Render.Environment
  ;

type

  TSyJavascriptMethodCallBack = function(const obj: ICefv8Value; const arguments: TCefv8ValueArray;
      var retval: ICefv8Value; var exception: ustring): Boolean of object;

  TSyV8Handler = class(TCefv8HandlerOwn)
  protected
    FContext  : ICefv8Context;
    FProcess  : TSyRenderProcess;
    FBrowserId: Integer;
    FFrameId  : Integer;

    FJavascriptMethodMapper: TDictionary<string, TSyJavascriptMethodCallBack>;

    procedure DoExecute(Stub: TSyRenderJavascriptStub); overload;
    procedure DoExecute(Handler: TSyRenderJavascriptHandler); overload;
  protected
    function Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray;
      var retval: ICefv8Value; var exception: ustring): Boolean; override;
  protected
    procedure DefineObjectFunction(Ref: ICefv8Value; Name: string; CallBack: TSyJavascriptMethodCallBack);
  public
    constructor Create; override;
    destructor Destroy; override;
  public
    property Context: ICefv8Context read FContext;
    property Process: TSyRenderProcess read FProcess;
  end;

  TSyV8PropertyAccessor = class(TCefV8AccessorOwn)
  protected
    function Get(const name: ustring; const obj: ICefv8Value; out value: ICefv8Value;
      const exception: ustring): Boolean; override;
    function Put(const name: ustring; const obj, value: ICefv8Value; const exception: ustring): Boolean; override;
  end;

  TSyV8ObjectHandler = class(TSyV8Handler)
  private
    FObjectRef: TValue;
  protected
    procedure CreateJavascriptMethods(javascriptObject: ICefv8Value);
    procedure CreateJavascriptProperties(javascriptObject: ICefv8Value);
  protected
    function Execute(const name: ustring; const obj: ICefv8Value;
      const arguments: TCefv8ValueArray; var retval: ICefv8Value;
      var exception: ustring): Boolean; override;
  public
    class procedure Bind(context: ICefv8Context; name: string; obj: TValue; window: ICefV8Value);
  end;


  /// <summary>
  /// 对象描述导出处理器
  /// </summary>
  TSyV8ObjectManifestHandler = class(TSyV8Handler)
  private
    FObjectManifest: TSyObjectInvokeManifest;
  protected
    procedure CreateJavascriptMethods(javascriptObject: ICefv8Value);
    procedure CreateJavascriptProperties(javascriptObject: ICefv8Value);
  protected
    function Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray;
      var retval: ICefv8Value; var exception: ustring): Boolean; override;
  public
    class procedure Bind(Sender: TSyRenderProcessHandler; const browser: ICefBrowser; const frame: ICefFrame;
      const context: ICefv8Context; manifest: TSyObjectInvokeManifest);
  end;

implementation

  uses Chromium4D.Utils;

{ TSyV8ObjectHandler }

class procedure TSyV8ObjectHandler.Bind(context: ICefv8Context; name: string; obj: TValue; window: ICefV8Value);
var
  handler: TSyV8ObjectHandler;
  javascriptWrapper: ICefv8Value;
begin
  handler := TSyV8ObjectHandler.Create;
  handler.FContext   := context;
  handler.FObjectRef := obj;

  javascriptWrapper := TCefv8ValueRef.NewObject(TSyV8PropertyAccessor.Create);

  handler.CreateJavascriptMethods(javascriptWrapper);
  handler.CreateJavascriptProperties(javascriptWrapper);

  window.SetValueByKey(name, javascriptWrapper, []);

  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8ObjectHandler.Bind name: %s', [name]);
end;

procedure TSyV8ObjectHandler.CreateJavascriptMethods(javascriptObject: ICefv8Value);
begin
end;

procedure TSyV8ObjectHandler.CreateJavascriptProperties(javascriptObject: ICefv8Value);
begin

end;

function TSyV8ObjectHandler.Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray;
  var retval: ICefv8Value; var exception: ustring): Boolean;
begin
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8ObjectHandler.Execute name: %s', [name]);
end;

{ TSyV8PropertyAccessor }

function TSyV8PropertyAccessor.Get(const name: ustring; const obj: ICefv8Value; out value: ICefv8Value;
  const exception: ustring): Boolean;
begin
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8PropertyAccessor.Get name:  %s', [name]);
end;

function TSyV8PropertyAccessor.Put(const name: ustring; const obj, value: ICefv8Value;
  const exception: ustring): Boolean;
begin
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8PropertyAccessor.Put name:  %s', [name]);
end;

{ TSyV8Handler }

constructor TSyV8Handler.Create;
begin
  inherited;
  FContext := nil;
  FProcess := nil;
  FJavascriptMethodMapper := TDictionary<string, TSyJavascriptMethodCallBack>.Create();
end;

procedure TSyV8Handler.DefineObjectFunction(Ref: ICefv8Value; Name: string; CallBack: TSyJavascriptMethodCallBack);
begin
  FJavascriptMethodMapper.Add(Name, CallBack);
  Ref.SetValueByKey(Name, TCefv8ValueRef.NewFunction(Name, Self), []);
end;

destructor TSyV8Handler.Destroy;
begin
  if Assigned(FJavascriptMethodMapper) then
    FreeAndNil(FJavascriptMethodMapper);
  FContext := nil;
  FProcess := nil;
  inherited;
end;

procedure TSyV8Handler.DoExecute(Stub: TSyRenderJavascriptStub);
begin
  Process.ProcessHandler(TSyRenderJavascriptHandler.Create(Stub));
end;

procedure TSyV8Handler.DoExecute(Handler: TSyRenderJavascriptHandler);
begin
  Process.ProcessHandler(Handler);
end;

function TSyV8Handler.Execute(const name: ustring; const obj: ICefv8Value; const arguments: TCefv8ValueArray;
  var retval: ICefv8Value; var exception: ustring): Boolean;
var
  CallBack: TSyJavascriptMethodCallBack;
begin
  if not FJavascriptMethodMapper.TryGetValue(name, CallBack) then
  begin
    exception := Format('find callbakc %s failed', [name]);
    Logger.Error(TCefThreadHelper.CheckCurrentThread + ' | ' + exception);
    Exit(True)
  end;

  Result := CallBack(obj, arguments, retval, exception);
end;

{ TSyV8ObjectManifestHandler }

class procedure TSyV8ObjectManifestHandler.Bind(Sender: TSyRenderProcessHandler; const browser: ICefBrowser; const frame: ICefFrame;
      const context: ICefv8Context; manifest: TSyObjectInvokeManifest);
var
  handler: TSyV8ObjectManifestHandler;
  javascriptWrapper: ICefv8Value;
begin
  handler                 := TSyV8ObjectManifestHandler.Create;
  handler.FProcess        := Sender.GetRenderProcess;
  handler.FContext        := context;
  handler.FBrowserId      := browser.Identifier;
  handler.FFrameId        := frame.GetIdentifier;

  handler.FObjectManifest := manifest;

  javascriptWrapper := TCefv8ValueRef.NewObject(TSyV8PropertyAccessor.Create);

  handler.CreateJavascriptMethods(javascriptWrapper);
  handler.CreateJavascriptProperties(javascriptWrapper);

  context.Global.SetValueByKey(manifest.Namespace, javascriptWrapper, []);

  Logger.Info(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8ObjectManifestHandler.Bind Object: %s', [manifest.Namespace]);
end;

procedure TSyV8ObjectManifestHandler.CreateJavascriptMethods(javascriptObject: ICefv8Value);
var
  I: Integer;
  method: TSyObjectInvokeMethod;
begin
  for I := Low(FObjectManifest.Methods) to High(FObjectManifest.Methods) do
  begin
    method := FObjectManifest.Methods[I];
    javascriptObject.SetValueByKey(method.MethodName, TCefv8ValueRef.NewFunction(method.MethodName, Self), [V8_PROPERTY_ATTRIBUTE_DONTDELETE])
  end;
end;

procedure TSyV8ObjectManifestHandler.CreateJavascriptProperties(javascriptObject: ICefv8Value);
var
  I: Integer;
  Attribute: TSyObjectInvokeAttribute;
  pa: TCefV8PropertyAttributes;
  ac: TCefV8AccessControls;
begin
  for Attribute in FObjectManifest.Attributes do
  begin
    pa := [V8_PROPERTY_ATTRIBUTE_DONTDELETE];
    if not Attribute.IsSetter then
      pa := pa + [V8_PROPERTY_ATTRIBUTE_READONLY];

    ac := [];
    if Attribute.IsGetter then
      ac := ac + [V8_ACCESS_CONTROL_ALL_CAN_READ];
    if Attribute.IsSetter then
      ac := ac + [V8_ACCESS_CONTROL_ALL_CAN_WRITE];

    javascriptObject.SetValueByAccessor(Attribute.AttributeName, ac, pa);
  end;
end;

function TSyV8ObjectManifestHandler.Execute(const name: ustring; const obj: ICefv8Value;
  const arguments: TCefv8ValueArray; var retval: ICefv8Value; var exception: ustring): Boolean;
begin
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  TSyV8ObjectManifestHandler.Execute name: %s', [name]);
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  >>> obj information');
  TCefv8ValueHelper.Display(obj, 'obj', 2);
  Logger.Trace(TCefThreadHelper.CheckCurrentThread + ' | ' +'  >>> arguments information');
  TCefv8ValueArrayHelper.Display(arguments, 'arguments');
end;

end.

